
import os, sys
import Tkinter as tk
import tkFileDialog
import bootloader

class bootloadergui:

    def __init__(self):
        self.flash = []
        self.importingfile=None
        for i in range(0x6000):
            self.flash.append(0xFF)

        self.eeprom = []
        for i in range(0x100):
            self.eeprom.append(0xFF)

        self.root = tk.Tk()
        self.root.title('USB Bootloader GUI')

        self.write_bootloader_on_export = tk.BooleanVar()
        self.write_bootloader_on_export.set(0)

        self.write_eeprom_on_export = tk.BooleanVar()
        self.write_eeprom_on_export.set(0)

        self.verify_on_write = tk.BooleanVar()
        self.verify_on_write.set(1)

        self.clear_buffers_on_erase = tk.BooleanVar()
        self.clear_buffers_on_erase.set(1)
        
        self.display_bootloader = tk.BooleanVar()
        self.display_bootloader.set(0)

        self.menubar = tk.Menu(self.root)
        self.filemenu = tk.Menu(self.menubar, tearoff=0)
        self.filemenu.add_command(label='Import Hex...', command=self.import_hex)
        self.filemenu.add_command(label='Export Hex...', command=self.export_hex)
        self.filemenu.add_separator()
        self.filemenu.add_checkbutton(label='Write Bootloader on Export Hex', variable=self.write_bootloader_on_export)
        self.filemenu.add_checkbutton(label='Write EEPROM on Export Hex', variable=self.write_eeprom_on_export)
        self.filemenu.add_separator()
        self.filemenu.add_command(label='Quit', command=self.exit)
        self.menubar.add_cascade(label='File', menu=self.filemenu)
        self.bootloadermenu = tk.Menu(self.menubar, tearoff=0)
        self.bootloadermenu.add_command(label='Read Device', command=self.read_device)
        self.bootloadermenu.add_command(label='Write Device', command=self.write_device)
        self.bootloadermenu.add_command(label='Verify', command=self.verify)
        self.bootloadermenu.add_command(label='Erase', command=self.erase)
        self.bootloadermenu.add_command(label='Blank Check', command=self.blank_check)
        self.bootloadermenu.add_separator()
        self.bootloadermenu.add_command(label='Read EEPROM', command=self.read_eeprom)
        self.bootloadermenu.add_command(label='Write EEPROM', command=self.write_eeprom)
        self.bootloadermenu.add_separator()
        self.bootloadermenu.add_checkbutton(label='Verify on Write', variable=self.verify_on_write)
        self.bootloadermenu.add_checkbutton(label='Clear Memory Buffers on Erase', variable=self.clear_buffers_on_erase)
        self.bootloadermenu.add_separator()
        self.bootloadermenu.add_command(label='Connect', command=self.connect)
        self.bootloadermenu.add_command(label='Disconnect/Run', command=self.disconnect)
        self.menubar.add_cascade(label='Bootloader', menu=self.bootloadermenu)
        self.root.config(menu=self.menubar)
        
        self.statusdisplay = tk.Text(self.root, height=2, width=70, font=('Courier', 10), background='#71FF71', state=tk.DISABLED)
        self.statusdisplay.pack(anchor=tk.NW, padx=10, pady=10)
        
        self.buttonframe = tk.Frame(self.root)
        self.readbutton = tk.Button(self.buttonframe, text='Read', command=self.read_device)
        self.readbutton.pack(side=tk.LEFT, padx=5)
        self.writebutton = tk.Button(self.buttonframe, text='Write', command=self.write_device)
        self.writebutton.pack(side=tk.LEFT, padx=5)
        self.verifybutton = tk.Button(self.buttonframe, text='Verify', command=self.verify)
        self.verifybutton.pack(side=tk.LEFT, padx=5)
        self.erasebutton = tk.Button(self.buttonframe, text='Erase', command=self.erase)
        self.erasebutton.pack(side=tk.LEFT, padx=5)
        self.blankchkbutton = tk.Button(self.buttonframe, text='Blank Check', command=self.blank_check)
        self.blankchkbutton.pack(side=tk.LEFT, padx=5)
        self.connectbutton = tk.Button(self.buttonframe, text='Connect', command=self.connect)
        self.connectbutton.pack(side=tk.LEFT, padx=5)
        self.disconnectbutton = tk.Button(self.buttonframe, text='Disconnect/Run', command=self.disconnect)
        self.disconnectbutton.pack(side=tk.LEFT, padx=5)
        self.doitallbutton = tk.Button(self.buttonframe, text='Do it all', command=self.doitall)
        self.doitallbutton.pack(side=tk.LEFT, padx=5)
        self.buttonframe.pack(side=tk.TOP, anchor=tk.NW, padx=5)
        
        self.flashdispframe = tk.LabelFrame(self.root, text='Program Memory', padx=5, pady=5)
        self.flashdispframe.pack(anchor=tk.NW, padx=10, pady=10)
        self.flashdisplaybootloader = tk.Checkbutton(self.flashdispframe, text='Display Bootloader', variable=self.display_bootloader, command=self.update_flash_display)
        self.flashdisplaybootloader.pack(side=tk.TOP, anchor=tk.NW)
        self.flashdisplay = tk.Frame(self.flashdispframe)
        self.flashtext = tk.Text(self.flashdisplay, height=16, width=70, font=('Courier', 10))
        self.flashscrollbar = tk.Scrollbar(self.flashdisplay, command=self.flashtext.yview)
        self.flashtext.configure(yscrollcommand=self.flashscrollbar.set)
        self.flashtext.pack(side=tk.LEFT)
        self.flashscrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.flashdisplay.pack()
        self.update_flash_display()
        
        self.eepromdispframe = tk.LabelFrame(self.root, text='EEPROM Data', padx=5, pady=5)
        self.eepromdispframe.pack(anchor=tk.NW, padx=10, pady=10)
#        self.eeprombuttonframe = tk.Frame(self.eepromdispframe)
#        self.eepromreadbutton = tk.Button(self.eeprombuttonframe, text='Read', command=self.read_eeprom)
#        self.eepromreadbutton.pack(side=tk.LEFT, pady=5)
#        self.eepromwritebutton = tk.Button(self.eeprombuttonframe, text='Write', command=self.write_eeprom)
#        self.eepromwritebutton.pack(side=tk.LEFT, padx=10, pady=5)
#        self.eeprombuttonframe.pack(side=tk.TOP, anchor=tk.NW)
        self.eepromdisplay = tk.Frame(self.eepromdispframe)
        self.eepromtext = tk.Text(self.eepromdisplay, height=8, width=70, font=('Courier', 10))
        self.eepromscrollbar = tk.Scrollbar(self.eepromdisplay, command=self.eepromtext.yview)
        self.eepromtext.configure(yscrollcommand=self.eepromscrollbar.set)
        self.eepromtext.pack(side=tk.LEFT)
        self.eepromscrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.eepromdisplay.pack()
        self.update_eeprom_display()

        self.connected = False
        self.connect()

    def bootloadermenu_disconnected(self):
        self.bootloadermenu.entryconfig(0, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(1, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(2, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(3, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(4, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(6, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(7, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(12, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(13, state=tk.DISABLED)
        
        self.readbutton.config(state=tk.DISABLED)
        self.writebutton.config(state=tk.DISABLED)
        self.verifybutton.config(state=tk.DISABLED)
        self.erasebutton.config(state=tk.DISABLED)
        self.blankchkbutton.config(state=tk.DISABLED)
        self.connectbutton.config(state=tk.NORMAL)
        self.disconnectbutton.config(state=tk.DISABLED)
#        self.eepromreadbutton.config(state=tk.DISABLED)
#        self.eepromwritebutton.config(state=tk.DISABLED)

    def bootloadermenu_connected(self):
        self.bootloadermenu.entryconfig(0, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(1, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(2, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(3, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(4, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(6, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(7, state=tk.NORMAL)
        self.bootloadermenu.entryconfig(12, state=tk.DISABLED)
        self.bootloadermenu.entryconfig(13, state=tk.NORMAL)

        self.readbutton.config(state=tk.NORMAL)
        self.writebutton.config(state=tk.NORMAL)
        self.verifybutton.config(state=tk.NORMAL)
        self.erasebutton.config(state=tk.NORMAL)
        self.blankchkbutton.config(state=tk.NORMAL)
        self.connectbutton.config(state=tk.DISABLED)
        self.disconnectbutton.config(state=tk.NORMAL)
#        self.eepromreadbutton.config(state=tk.NORMAL)
#        self.eepromwritebutton.config(state=tk.NORMAL)

    def display_message(self, message, clear_display = True):
        self.statusdisplay.config(state=tk.NORMAL)
        if clear_display==True:
            self.statusdisplay.delete(1.0, tk.END)
        self.statusdisplay.config(background='#71FF71')
        self.statusdisplay.insert(tk.END, message)
        self.statusdisplay.config(state=tk.DISABLED)
        self.statusdisplay.update()
    
    def display_warning(self, warning, clear_display = True):
        self.statusdisplay.config(state=tk.NORMAL)
        if clear_display==True:
            self.statusdisplay.delete(1.0, tk.END)
        self.statusdisplay.config(background='#FFFF71')
        self.statusdisplay.insert(tk.END, warning)
        self.statusdisplay.config(state=tk.DISABLED)
        self.statusdisplay.update()
    
    def display_error(self, error, clear_display = True):
        self.statusdisplay.config(state=tk.NORMAL)
        if clear_display==True:
            self.statusdisplay.delete(1.0, tk.END)
        self.statusdisplay.config(background='#FF7171')
        self.statusdisplay.insert(tk.END, error)
        self.statusdisplay.config(state=tk.DISABLED)
        self.statusdisplay.update()

    def exit(self):
        sys.exit(0)

    def write_device(self):
        self.display_warning('Erasing program memory')
        for address in range(0x0B00, 0x6000, 64):
            self.bootloader.erase_flash(address)
            if (address%512)==0:
                self.display_warning('.', False)
        self.display_warning('Writing program memory')
        for address in range(0x0B00, 0x6000, 32):
            bytes = []
            for i in range(32):
                bytes.append(self.flash[address+i])
            for i in range(32):
                if bytes[i]!=0xFF:
                    break
            else:
                continue
            self.bootloader.write_flash(address, bytes)
            if (address%512)==0:
                self.display_warning('.', False)
        self.display_warning('\nWriting data EEPROM...', False)
        for address in range(0x100):
            self.bootloader.write_eeprom(address, [self.eeprom[address]])
        if self.verify_on_write.get()==1:
            if self.verify()==0:
                self.display_message('Write completed successfully.')
        else:
            self.display_warning('Write completed, but not verified.')
    
    def read_device(self):
        self.display_warning('Reading program memory')
        for address in range(0x0000, 0x6000, 64):
            bytes = self.bootloader.read_flash(address, 64)
            for j in range(len(bytes)):
                self.flash[address+j] = bytes[j]
            if (address%512)==0:
                self.display_warning('.', False)
        self.display_warning('\nReading data EEPROM...', False)
        for address in range(0x0000, 0x0100, 64):
            bytes = self.bootloader.read_eeprom(address, 64)
            for j in range(len(bytes)):
                self.eeprom[address+j] = bytes[j]
        self.display_message('Read device completed successfully.')
        self.update_flash_display()
        self.update_eeprom_display()
    
    def verify(self):
        self.display_warning('Verifying program memory')
        for address in range(0x0B00, 0x6000, 64):
            if (address%512)==0:
                self.display_warning('.', False)
            bytes = self.bootloader.read_flash(address, 64)
            for i in range(64):
                if bytes[i]!=self.flash[address+i]:
                    break
            else:
                continue
            self.display_error('Verification failed.\nRead 0x%02X at location 0x%04X in program memory, expecting 0x%02X.' % (bytes[i], address+i, self.flash[address+i]))
            return -1
        self.display_warning('\nVerifying data EEPROM...', False)
        for address in range(0x0000, 0x0100, 64):
            bytes = self.bootloader.read_eeprom(address, 64)
            for i in range(64):
                if bytes[i]!=self.eeprom[address+i]:
                    break
            else:
                continue
            self.display_error('Verification failed.\nRead 0x%02X at location 0x%04X in data EEPROM, expecting 0x%02X.' % (bytes[i], address+i, self.eeprom[address+i]))
            return -1
        self.display_message('Verification succeeded.')
        return 0

    def erase(self):
        self.display_warning('Erasing program memory')
        for address in range(0x0B00, 0x6000, 64):
            self.bootloader.erase_flash(address)
            if (address%512)==0:
                self.display_warning('.', False)
        self.display_warning('\nErasing data EEPROM...', False)
        for address in range(0x100):
            self.bootloader.write_eeprom(address, [0xFF])
        self.display_message('Device erased sucessfully.')
        if self.clear_buffers_on_erase.get()==1:
            self.clear_flash()
            self.clear_eeprom()
            self.update_flash_display()
            self.update_eeprom_display()

    def blank_check(self):
        self.display_warning('Checking program memory')
        for address in range(0x0B00, 0x6000, 64):
            if (address%512)==0:
                self.display_warning('.', False)
            bytes = self.bootloader.read_flash(address, 64)
            for i in range(64):
                if bytes[i]!=0xFF:
                    break
            else:
                continue
            self.display_error('Device is not blank.\nRead 0x%02X at location 0x%04X in program memory.' % (bytes[i], address+i))
            return
        self.display_warning('\nChecking data EEPROM...', False)
        for address in range(0x0000, 0x0100, 64):
            bytes = self.bootloader.read_eeprom(address, 64)
            for i in range(64):
                if bytes[i]!=0xFF:
                    break
            else:
                continue
            self.display_error('Device is not blank.\nRead 0x%02X at location 0x%04X in data EEPROM.' % (bytes[i], address+i))
            return
        self.display_message('Device is blank.')

    def write_eeprom(self):
        self.display_warning('Writing data EEPROM...')
        for address in range(0x100):
            self.bootloader.write_eeprom(address, [self.eeprom[address]])
        if self.verify_on_write.get()==1:
            self.display_warning('Verifying data EEPROM...')
            for address in range(0x0000, 0x0100, 64):
                bytes = self.bootloader.read_eeprom(address, 64)
                for i in range(64):
                    if bytes[i]!=self.eeprom[address+i]:
                        break
                else:
                    continue
                self.display_error('Verification failed.\nRead 0x%02X at location 0x%04X in data EEPROM, expecting 0x%02X.' % (bytes[i], address+i, self.eeprom[address+i]))
                return
            self.display_message('Write data EEPROM succeeded.')
        else:
            self.display_warning('Write data EEPROM compleded, but not verified.')

    def read_eeprom(self):
        self.display_warning('Reading data EEPROM...')
        for address in range(0x0000, 0x0100, 64):
            bytes = self.bootloader.read_eeprom(address, 64)
            for j in range(len(bytes)):
                self.eeprom[address+j] = bytes[j]
        self.display_message('Read data EEPROM completed successfully.')
        self.update_eeprom_display()
    
    def connect(self):
        self.bootloader = bootloader.bootloader()
        if self.bootloader.dev>=0:
            self.connected = True
            self.display_message('Connected to a USB bootloader device.')
            self.bootloadermenu_connected()
        else:
            self.connected = False
            self.display_error('Could not connect to a USB bootloader device.\nPlease connect one and then select Bootloader > Connect to proceed.')
            self.bootloadermenu_disconnected()
    
    def disconnect(self):
        self.bootloader.start_user()
        self.bootloader.close()
        self.connected = False
        self.bootloadermenu_disconnected()
        self.display_message('Disconnected from USB bootloader device.\nPush button to start user code.')
        
    def doitall(self):
        self.connect()
        if self.connected:
            self.import_hex(True)
            self.write_device()
            self.disconnect()
        
    def clear_flash(self):
        for i in range(0x6000):
            self.flash[i] = 0xFF

    def clear_eeprom(self):
        for i in range(0x100):
            self.eeprom[i] = 0xFF

    def update_flash_display(self):
        self.flashtext.config(state=tk.NORMAL)
        self.flashtext.delete(1.0, tk.END)
        if self.display_bootloader.get()==1:
            start = 0x0000
        else:
            start = 0x0B00
        first = True
        for address in range(start, 0x6000, 16):
            if first==True:
                first = False
                line = ''
            else:
                line = '\n'
            line = line + ('%04X: ' % address)
            for i in range(16):
                line = line + ('%02X ' % self.flash[address+i])
            for i in range(16):
                if (self.flash[address+i]>=32) and (self.flash[address+i]<127):
                    line = line + ('%c' % chr(self.flash[address+i]))
                else:
                    line = line + '.'
            self.flashtext.insert(tk.END, line)
        self.flashtext.config(state=tk.DISABLED)
    
    def update_eeprom_display(self):
        self.eepromtext.config(state=tk.NORMAL)
        self.eepromtext.delete(1.0, tk.END)
        first = True
        for address in range(0x00, 0x100, 16):
            if first==True:
                first = False
                line = ''
            else:
                line = '\n'
            line = line + ('%04X: ' % address)
            for i in range(16):
                line = line + ('%02X ' % self.eeprom[address+i])
            for i in range(16):
                if (self.eeprom[address+i]>=32) and (self.eeprom[address+i]<127):
                    line = line + ('%c' % chr(self.eeprom[address+i]))
                else:
                    line = line + '.'
            self.eepromtext.insert(tk.END, line)
        self.eepromtext.config(state=tk.DISABLED)

    def import_hex(self, usedefault=False):
        filename=''
        if ((not usedefault) or (not self.importingfile) or self.importingfile==''):
            filename = tkFileDialog.askopenfilename(parent=self.root, title='Import Hex File', defaultextension='.hex', filetypes=[('HEX file', '*.hex'), ('All files', '*')])
            self.importingfile=filename
        else:
            filename =self.importingfile
            print filename
        if filename=='':
            print "import quitting"
            return
        hexfile = open(filename, 'r')
        self.clear_flash()
        eeprom_data_seen = False
        address_high = 0
        for line in hexfile:
            line = line.strip('\n')
            byte_count = int(line[1:3], 16)
            address_low = int(line[3:7], 16)
            record_type = int(line[7:9], 16)
            if record_type==0:
                address = (address_high<<16)+address_low
                for i in range(byte_count):
                    if (address>=0) and (address<0x2A):
                        self.flash[address+0xB00] = int(line[9+2*i:11+2*i], 16)
                    elif (address>=0xB00) and (address<0x6000):
                        self.flash[address] = int(line[9+2*i:11+2*i], 16)
                    elif (address>=0xF00000) and (address<0xF00100):
                        if eeprom_data_seen==False:
                            eeprom_data_seen = True
                            self.clear_eeprom()
                        self.eeprom[address&0xFF] = int(line[9+2*i:11+2*i], 16)
                    address = address+1
            elif record_type==4:
                address_high = int(line[9:13], 16)
        hexfile.close()
        self.update_flash_display()
        self.update_eeprom_display()

    def export_hex(self):
        filename = tkFileDialog.asksaveasfilename(parent=self.root, title='Export Hex File', defaultextension='.hex', filetypes=[('HEX file', '*.hex'), ('All files', '*')])
        if filename=='':
            return
        hexfile = open(filename, 'w')
        hexfile.write(':020000040000FA\n')
        if self.write_bootloader_on_export.get()==1:
            for address in range(0x0000, 0x0B00, 16):
                for i in range(15, -1, -1):
                    if self.flash[address+i]!=0xFF:
                        end = i
                        break
                else:
                    continue
                for i in range(16):
                    if self.flash[address+i]!=0xFF:
                        start = i
                        break
                line = ':%02X%04X00' % (end-start+1, address+start)
                checksum = (end-start+1) + ((address+start)>>8) + ((address+start)&0xFF)
                for i in range(start, end+1):
                    line = line + '%02X' % self.flash[address+i]
                    checksum = checksum + self.flash[address+i]
                line = line + '%02X\n' % (0x100-(checksum&0xFF))
                hexfile.write(line)
        for address in range(0x0B00, 0x6000, 16):
            for i in range(15, -1, -1):
                if self.flash[address+i]!=0xFF:
                    end = i
                    break
            else:
                continue
            for i in range(16):
                if self.flash[address+i]!=0xFF:
                    start = i
                    break
            line = ':%02X%04X00' % (end-start+1, address+start)
            checksum = (end-start+1) + ((address+start)>>8) + ((address+start)&0xFF)
            for i in range(start, end+1):
                line = line + '%02X' % self.flash[address+i]
                checksum = checksum + self.flash[address+i]
            line = line + '%02X\n' % (0x100-(checksum&0xFF))
            hexfile.write(line)
        if self.write_eeprom_on_export.get()==1:
            hexfile.write(':0200000400F00A\n')
            for address in range(0x00, 0x100, 16):
                for i in range(15, -1, -1):
                    if self.eeprom[address+i]!=0xFF:
                        end = i
                        break
                else:
                    continue
                for i in range(16):
                    if self.eeprom[address+i]!=0xFF:
                        start = i
                        break
                line = ':%02X%04X00' % (end-start+1, address+start)
                checksum = (end-start+1) + ((address+start)>>8) + ((address+start)&0xFF)
                for i in range(start, end+1):
                    line = line + '%02X' % self.eeprom[address+i]
                    checksum = checksum + self.eeprom[address+i]
                line = line + '%02X\n' % (0x100-(checksum&0xFF))
                hexfile.write(line)
        hexfile.write(':00000001FF\n')
        hexfile.close()

if __name__=='__main__':
    boot = bootloadergui()
    boot.root.mainloop()
